package com.sensePlusVR.utils;


import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.sensePlusVR.bean.GameUrl;

public class S3Download extends Thread{
	private String TAG="S3Download";
    private AmazonS3 s3;
    private String bucket;
    private String key;
	private String url;
    private String savePath;
    private String saveFile;
    private long totalLen;
    private long doneLen;
    private long tempDone;
    private int progress;
    private Listener listener;
    private Object caller;
    public static final int DownloadEventInited = 0;
    public static final int DownloadEventError = 1;
    public static final int DownloadEventProgress = 2;
    public static final int DownloadEventFinished = 3;
    public static final int DownloadEventSpeed = 4;
    private final int DELAY=100;
    private final int WAITTIME=1000;
    private InputStream inStream;
    private WriteThread mThread;
    private BlockingQueue<ByteBuffer> mData = new LinkedBlockingQueue<ByteBuffer>(1000);
    private boolean mWriteExit;
    private FileOutputStream fileWriter;
    //锁
    private Object lock = new Object();
    //private Lock mLock = new ReentrantLock();
    
    private void putData(ByteBuffer buf){
    	do {
    		try {
    			mData.put(buf);
    			break;
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			MyLog.e(TAG, "put data exception, do again");
    			e.printStackTrace();
    		}
    	}while(true);
    }
        
    private byte[] getData(){
    	byte [] data = null;

    	ByteBuffer b = null;
		try {
			b = mData.take();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	
    	if (b != null){
    		data = b.array();
    	}
    	return data;
    }
    
    public class WriteThread extends Thread{

    	public void run() {
    		while(true){
				byte[] data = getData();
				if (data == null) {
					MyLog.e(TAG, "get date null, continue");
					continue;
				}
				if (data[0] == 0x01){
					MyLog.e(TAG, "download exit cmd, exit write thread");
					break;
				}
				boolean exception = false;
				if (fileWriter != null) {
					try {
						fileWriter.write(data, 1, data.length-1);
						//MyLog.e(TAG, "write len="+(data.length-1));
						doneLen += (data.length-1);
						//MyLog.e(TAG, "get totalLen="+totalLen+" doneLen="+doneLen+"  wirte download");
						notifyProgress();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						//e.printStackTrace();
						MyLog.e(TAG, "write error");
						exception = true;
					}
				}
				if (exception){
					break;
				}
    		}
    		mWriteExit = true;
    	}
    }
    public void cancelDownload(){
    	
    	synchronized (lock) {
			
    		if (inStream != null){
        		try {
    				inStream.close();
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
        	}
		}
    
    }
    
    public long getTotalLen(){
        return totalLen;
    }
    
    public long getDoneLen(){
        return doneLen;
    }
    
    public int getProgress(){
        if (totalLen == 0 || doneLen == 0)
            return 0;
        int p =  (int)((doneLen*100)/totalLen);
        if (p == 100)
        	savefile();
        return p;
    }
    
    
    private void notifyInited(){
        if (listener != null)
            listener.notify(DownloadEventInited, totalLen, caller);
    }
    private void notifyError(){
        if (listener != null)
            listener.notify(DownloadEventError, 0, caller);
    }
    private void notifyProgress(){
        int currProgress = getProgress();
        //MyLog.e(TAG, "saveFile="+saveFile+" progress="+currProgress);
        if (currProgress == progress){
            return;
        }
        progress = currProgress;
        if (listener != null)
            listener.notify(DownloadEventProgress, getProgress(), caller);
    }
    private void dotifyFinished(){
        if (listener != null)
            listener.notify(DownloadEventFinished, 0, caller);
    }
    
    private void notifySpeed(long speed){
        if (listener != null)
            listener.notify(DownloadEventSpeed, speed, caller);    	
    }
    private String tempFile(){
        return saveFile+".tmp";
    }
    
    private void finished(){
    	MyLog.e(TAG,"download file"+ saveFile +"is sucess");
        dotifyFinished();
    }
    
	private void savefile() {
		File tempfile = new File(tempFile());

		//if not a zip file
        if (tempfile.exists()){
        	
        	long totalLen = tempfile.length();
        	File savefile = new File(saveFile);
        	//if (savefile.exists())
        	//	savefile.delete();
            tempfile.renameTo(savefile);
            int delay = 0;
            while(!savefile.exists()){
            	try {
					Thread.sleep(DELAY);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
            	delay += DELAY;
            	MyLog.v(TAG, "wait file "+savefile.getAbsolutePath() +" to be saved!! delay="+delay);           	
            	if (delay >= WAITTIME)
            		break;
            }
            
            //保存md5
            GameUrl gameUrl = GameUrl.query(url);
            
            if(gameUrl!=null&&savefile.exists()){
            	
            	String md5 = MD5.getMd5(savefile);
            	
            	gameUrl.setTotalLength(totalLen);
            	
            	gameUrl.setMd5(md5);
            	
            	GameUrl.saveOrUpdate(gameUrl);
            	
            }
            
        }    	
    }
    
    private void downloadFinished(){
        savefile();
        File savefile = new File(saveFile);
        if (savefile.exists())
        	finished();
    }
    
    public S3Download(String url, String savePath){ 
        AWSCredentials credentials = new AWSCredentials(){

            @Override
            public String getAWSAccessKeyId() {
                // TODO Auto-generated method stub
            	//AKIAJOFFTGC6DKQVRZAA 为外网域名
                return "AKIAJOFFTGC6DKQVRZAA";///"AKIAJOFFTGC6DKQVRZAA";//"AKIAIZE3ZG6OH57YVKLQ";
            }

            @Override
            public String getAWSSecretKey() {
                // TODO Auto-generated method stub
            	//5iF9aXTTNxCyVuxMoiu8wmN0ZnbKXg+CI9bwj6Qp 为外网域名
                return "5iF9aXTTNxCyVuxMoiu8wmN0ZnbKXg+CI9bwj6Qp";//"5iF9aXTTNxCyVuxMoiu8wmN0ZnbKXg+CI9bwj6Qp";//"+ZwgiiiqWkicUqXlBbo9IpUcTaPVhAVv419KGTV9";
            }
            
        };
        
        s3 = new AmazonS3Client(credentials);
        String s3url = url.substring("https://".length());
        String reginBuckKey[] = s3url.split(".amazonaws.com/");
        String BuckKey[] = reginBuckKey[1].split("/");
        this.bucket = BuckKey[0];
        this.key = reginBuckKey[1].substring(bucket.length()+1, reginBuckKey[1].length());
        System.out.printf("== %s\r\n", reginBuckKey[1]);
        this.savePath = savePath;
        if (!key.contains("/")){
            this.saveFile = key;
        }else {
            String strs[] = key.split("/");
            this.saveFile = strs[strs.length-1];
        }
        this.saveFile = this.savePath + "/" + this.saveFile;
        Regions reg = Regions.US_WEST_1;
        if (reginBuckKey[0].equals("s3-us-west-2"))
            reg =  Regions.US_WEST_2;
        MyLog.e(TAG, "key=" + key + " file=" + saveFile);
        s3.setRegion(Region.getRegion(reg));
        
        totalLen = 0;
        doneLen = 0;
        tempDone = 0;
    }
    
    public void downloadPre(){
            S3Object object;
            File file = new File(saveFile);
            if (file.exists()){
            	doneLen = totalLen = file.length();
                return;
            }
            
            try {
                object = s3.getObject(new GetObjectRequest(bucket, key));//.withRange(0L, 0L));
            }catch (AmazonServiceException ase) {
                logase(ase);
                notifyError();
                return;
                //return ErrServ;
            } catch (AmazonClientException ace) {
                logace(ace);
                notifyError();
                return ;
            }  
            totalLen = object.getObjectMetadata().getContentLength();
            
            notifyInited();
            
            File tempFile = new File(tempFile());
            
            if (tempFile.exists()){
            	doneLen=tempDone = tempFile.length();
                if (tempDone == totalLen){
                    doneLen = totalLen;
                    savefile();
                }
            }
            try {
				object.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    }
    
    private void downloadFile(){

        S3Object object;
        File file = new File(saveFile);
        if (file.exists()){
        	doneLen = totalLen = file.length();
            finished();
            return;
        }
        try {
            object = s3.getObject(new GetObjectRequest(bucket, key));//.withRange(0L, 0L));
            
        }catch (AmazonServiceException ase) {
            logase(ase);
            notifyError();
            return;
            //return ErrServ;
        } catch (AmazonClientException ace) {
            logace(ace);
            notifyError();
            return ;
        }  
        totalLen = object.getObjectMetadata().getContentLength();
        MyLog.e(TAG, "get totalLen="+totalLen);
        notifyInited();
        
        File tempFile = new File(tempFile());
        long tempDone = 0;
        if (tempFile.exists()){
        	doneLen=tempDone = tempFile.length();
            if (tempDone == totalLen){
                doneLen = totalLen;
                downloadFinished();
                try {
					object.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
                return;
            }
        }
        boolean append = false;
        if (tempDone > 0){
            append = true;
            try {
                object.close();
                object = s3.getObject(new GetObjectRequest(bucket, key).withRange(tempDone, totalLen));
            }catch (AmazonServiceException ase) {
                logase(ase);
                notifyError();
                return;
                //return ErrServ;
            } catch (AmazonClientException ace) {
                logace(ace);
                notifyError();
                return ;
            } catch (IOException e) {
                // TODO Auto-generated catch block
                notifyError();
                MyLog.e(TAG, e.getMessage());
                return;
            }
        }
        FileOutputStream out=null;
        
        synchronized (lock) {
        	 inStream = object.getObjectContent();
		}
       
        try {
        	out = fileWriter = new FileOutputStream(tempFile, append);
        } catch (FileNotFoundException e1) {
            // TODO Auto-generated catch block
        	MyLog.e(TAG, e1.getMessage());
            notifyError();
            return;
        }
        mWriteExit = false;
        mThread = new WriteThread();
        mThread.start();
        doneLen = tempDone;

        try {
            long start = System.currentTimeMillis();;
            long donesize = 0;
            int len = 0;
            MyLog.e(TAG, "aws begin read......");
            do {
            //object.getObjectContent().skip(10);
            	byte[] b = new byte[16*1024];
            	b[0] = 0;
            	synchronized (lock) {	
            		len = inStream.read(b, 1, b.length-1);
				}
                if (len > 0){
                	ByteBuffer buff = ByteBuffer.allocate(len+1);
                	buff.put(b, 0, len+1);
                	putData(buff);
                }
                //MyLog.e(TAG, "read len="+len+" time:"+System.currentTimeMillis()/1000);
                if (len < 0){
                	break;
                }
                if (start == -1){
                	start = System.currentTimeMillis();
                }else{
                	donesize += len;
                	long end = System.currentTimeMillis();
                	long dal = end - start;
                	long speed = 0;
                	if (dal > 0){
                		speed = (long) (donesize/((float)dal/1000));
                		speed = speed / 1000;//KBps
                	}
                	if (dal >= 1000){
                		donesize = 0;
                		start = System.currentTimeMillis();
                		MyLog.e(TAG, "download speed="+speed);
                		notifySpeed(speed);
                	}
                }
            }while(len >= 0);
            
        } catch (IOException e) {
            // TODO Auto-generated catch block
        	MyLog.e(TAG, e.getMessage());
            try {
                out.close();
                synchronized (lock) {
                	 inStream.close();
				}
               
            } catch (IOException e1) {
                // TODO Auto-generated catch block
            	MyLog.e(TAG, e1.getMessage());
            }
            notifyError();
            ExitWriteThread();
            return;
        }
        ExitWriteThread();
        try {
        	synchronized (lock) {	
        		inStream.close();
			}
            out.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
        	MyLog.e(TAG, e.getMessage());
            notifyError();
            ExitWriteThread();
            return;
        }
        MyLog.e(TAG, "get totalLen="+totalLen+" doneLen="+doneLen+"  exit download");
        if (totalLen == doneLen){
            downloadFinished();
        }else {
            notifyError();
        }
    }
    
    private void ExitWriteThread(){
        if (mThread != null){
        	putExitCmd();
        	while(!mWriteExit){
        		try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
        	}
        	MyLog.e(TAG, "wirte thread exit...");
        }
    }
    private void putExitCmd(){
    	MyLog.e(TAG, "put exit cmd to write thread");
        byte []exit = new byte[1];
        exit[0] = 0x01;
    	ByteBuffer buff = ByteBuffer.wrap(exit);
    	putData(buff);
    }
    
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        downloadFile();
    }
    private void logase(AmazonServiceException ase){
    	//MyLog.e(TAG, "User cancel download");
    	MyLog.e(TAG, "Caught an AmazonServiceException, which means your request made it "
                + "to Amazon S3, but was rejected with an error response for some reason.");
    	MyLog.e(TAG, "Error Message:    " + ase.getMessage());
    	MyLog.e(TAG, "HTTP Status Code: " + ase.getStatusCode());
    	MyLog.e(TAG, "AWS Error Code:   " + ase.getErrorCode());
    	MyLog.e(TAG, "Error Type:       " + ase.getErrorType());
        MyLog.e(TAG, "Request ID:       " + ase.getRequestId());
    }

    private void logace(AmazonClientException ace) {
        MyLog.e(TAG, "Caught an AmazonClientException, which means the client encountered "
                + "a serious internal problem while trying to communicate with S3, "
                + "such as not being able to access the network.");
        MyLog.e(TAG, "Error Message: " + ace.getMessage());
    }
    
    public void setListener(Listener l, Object caller) {
        this.listener = l;
        this.caller = caller;
    }
    
    public interface Listener{
        void notify(int event, long data, Object o);
    }
}
